package game;

import dialogmaker.annotations.NumberRestrictions.Cap;
import dialogmaker.annotations.*;
import java.awt.Color;
import java.io.*;
import java.util.ArrayList;
import org.newdawn.slick.opengl.Texture;
import saves.*;

public class Data {

    public static final CorpoOrbitavel PLANETA_TERENT = null;
    public static final Faccao FACCAO_TERENT = null;
    public static final String ARQ_BLOCOS = "blocks.dat",
            ARQ_GASES = "gases.dat",
            ARQ_ELEMENTOS = "elements.dat",
            ARQ_ITENS = "items.dat",
            ARQ_ESPECIES = "especies.dat",
            ARQ_RACAS = "races.dat",
            ARQ_GALAXIA = "galaxy.dat";
    public static String CAMINHO = "data//";
    private static ArrayList<BlocoInfo> blocos;
    private static ArrayList<GasAtmosferico> gases;
    private static ArrayList<ElementoInfo> elementos;
    private static ArrayList<ItemInfo> itens;
    private static ArrayList<EspecieInfo> especies;
    private static ArrayList<RacaInfo> racas;
    private static GalaxiaInicial galaxia;

    private Data() {
        throw new AssertionError();
    }

    public static void load() {
        blocos = new ArrayList<>();
        gases = new ArrayList<>();
        elementos = new ArrayList<>();
        itens = new ArrayList<>();
        especies = new ArrayList<>();
        racas = new ArrayList<>();
        
        load(ARQ_BLOCOS, blocos);
        load(ARQ_GASES, gases);
        load(ARQ_ELEMENTOS, elementos);
        load(ARQ_ITENS, itens);
        load(ARQ_ESPECIES, especies);
        load(ARQ_RACAS, racas);
    }

    private static <T> void load(String file, ArrayList<T> itens) {
        if (!new File(CAMINHO + file).exists())
            return;
        try (ObjectInputStream leitor = new ObjectInputStream(new FileInputStream(CAMINHO + file))) {
            T b;
            while ((b = (T) leitor.readObject()) != null)
                itens.add(b);
        } catch (EOFException e) { //OK
        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Erro ao ler o arquivo " + file + "; error: " + e.getLocalizedMessage());
        }
    }

    public static void save() {
        save(ARQ_BLOCOS, blocos);
        save(ARQ_GASES, gases);
        save(ARQ_ELEMENTOS, elementos);
        save(ARQ_ITENS, itens);
        save(ARQ_ESPECIES, especies);
        save(ARQ_RACAS, racas);
    }

    private static void save(String arq, ArrayList<?> list) {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(CAMINHO + arq))) {
            for (int i = 0; i < list.size(); i++)
                out.writeObject(list.get(i));
        } catch (IOException ex) {
            System.err.println("Houve um erro ao salvar o arquivo: " + arq);
        }
    }

    public static float gerarCoefCalorAtm(double[] concentracoes) {
        float res = 0;
        for (int i = 0; i < concentracoes.length; i++)
            res += concentracoes[i] * gases.get(i).getCoefCalor();
        return res * 1;
    }

    private static int[] pickInts(int qtd, int lim) {
        if (qtd > lim)
            return null;
        int[] res = new int[qtd];
        for (int i = 0; i < qtd; i++) {
            res[i] = (int) (Math.random() * lim);
            for (int j = 0; j < i; j++)
                if (res[j] == res[i]) {
                    i--;
                    break;
                }
        }
        return res;
    }

    public static double[] gerarGasesRandomicamente() {
        double[] gs = new double[gases.size()];
        //Separando as atmosferas em 4 tipos - 2-only, 3-only, 2-most e 3-most
        int tipo = (int) Math.ceil(Math.random() * 4);
        int[] mainGases = pickInts(2 + tipo % 2, gases.size());

        double mgs = 0.1 * (tipo / 2), mper = (1 - mgs) / ((tipo % 2) + 1), msec = mgs / (gs.length - mainGases.length);
        for (int i = 0; i < mainGases.length; i++)
            gs[mainGases[i]] = mper + Math.random() * mper / 5 - mper / 10;

        double total = 0;
        for (int i = 0; i < gs.length; i++) {
            if (gs[i] == 0) {
                gs[i] = msec + Math.random() * msec / 5 - msec / 10;
                if (gs[i] < 0)
                    gs[i] = 0;
            }
            total += gs[i];
        }

        int last = gs.length - 1;
        gs[last] += 1 - total;
        while (last > 0 && gs[last] < 0) {
            gs[last - 1] += gs[last];
            gs[last] = 0;
            last--;
        }

        return gs;
    }

    public static GalaxiaInicial getGalaxia() {
        return galaxia;
    }

    public static ArrayList<GasAtmosferico> getGases() {
        return gases;
    }

    public static BlocoInfo getBlocoInfo(int codigo) {
        return blocos.get(codigo);
    }

    public static ArrayList<ElementoInfo> getElementos() {
        return elementos;
    }

    public static ArrayList<BlocoInfo> getBlocos() {
        return blocos;
    }

    public static int qtosBlocos() {
        return blocos.size();
    }

    public static ElementoInfo getElementoInfo(int codigo) {
        return elementos.get(codigo);
    }

    public static RacaInfo getRacaInfo(int codigo) {
        return racas.get(codigo);
    }

    public static RacaInfo[] getRacasInfo() {
        return (RacaInfo[]) racas.toArray();
    }

    public static EspecieInfo getEspecieInfo(int codigo) {
        return especies.get(codigo);
    }

    public static ItemInfo getItemInfo(int codigo) {
        return itens.get(codigo);
    }

    public static ItemInfo getItemInfo(String regex) {
        for (int i = 0; i < itens.size(); i++)
            if (itens.get(i).getRegex().equals(regex))
                return itens.get(i);
        return generateItemInfo(regex);
    }

    public static ItemInfo generateItemInfo(String regex) {
        //TODO
        return null;
    }

    public static ItemInfo getItemAleatorio(float raridade) {
        //TODO
        return null;
    }

    public static ItemInfo[] getItensAleatorios(float raridade, float qtd) {
        //TODO
        return null;
    }

    public static boolean temBorda(int codigo) {
        return blocos.get(codigo).temBorda();
    }

    public static class BlocoInfo implements Serializable {

        private static final long serialVersionUID = 4200319197694713096L;
        
        @RealName("Nome")
        private String nome;
        
        @RealName("Imagens")
        private ReferenciaImagem[] imagens;
        
        @RealName(value="Prioridade", description="Prioridade na hora de definir as bordas. Uma prioridade maior sempre fica por cima de uma menor.")
        private int prioridade;
        
        @RealName(value = "Resistência do Ar", description = "Valor de 0 a 1 relacionado ao atrito que o bloco causa na nave.\n0 é sem atrito, e 1 é atrito infinito (parede).")
        @NumberRestrictions(integer = false, min = 0, minCap = Cap.INCLUSIVE, max = 1, maxCap = Cap.INCLUSIVE)
        private float resistenciaAr;
        
        @RealName(value = "Coeficiente de Iluminação", description = "Valor de 0 a 1 que representa a opacidade de bloco.\n0 permite toda a luz passar, e 1, nenhuma.")
        @NumberRestrictions(integer = false, min = 0, minCap = Cap.INCLUSIVE, max = 1, maxCap = Cap.INCLUSIVE)
        private float coeficienteIluminacao;
        
        @RealName(value = "Iluminação Própria", description = "Valor de 0 a 1 que representa a emissão de luz do bloco.\n0 não emite luz, e 1 emite o máximo possível.")
        @NumberRestrictions(integer = false, min = 0, minCap = Cap.INCLUSIVE, max = 1, maxCap = Cap.INCLUSIVE)
        private float iluminacaoPropria;
        
        @RealName(value = "Fiação", description = "0: Não pode ter fiação\n1: Tem fiação pequena\n2: Tem fiação grande")
        @NumberRestrictions(integer = true, min = 0, minCap = Cap.INCLUSIVE, max = 2, maxCap = Cap.INCLUSIVE)
        private int fiacao; //0 - Não pode, 1 - Tem pequena, 2 - Tem grande
        
        @GroupName("Opções")
        @RealName(value = "Eletrocutável", description = "Determina se o bloco pode ser eletrocutado.")
        private boolean eletrocutavel;

        private BlocoInfo() { }

        public BlocoInfo(String nome, ReferenciaImagem[] imagens, int fiacao, float resistenciaAr, float coeficienteIluminacao, float iluminacaoPropria, boolean eletrocutavel) {
            this.nome = nome;
            this.imagens = imagens;
            this.fiacao = fiacao;
            this.resistenciaAr = resistenciaAr;
            this.coeficienteIluminacao = coeficienteIluminacao;
            this.iluminacaoPropria = iluminacaoPropria;
            this.eletrocutavel = eletrocutavel;
        }

        public String getNome() {
            return nome;
        }

        public ReferenciaImagem[] getImagens() {
            return imagens;
        }

        public int getFiacao() {
            return fiacao;
        }

        public float getResistenciaAr() {
            return resistenciaAr;
        }

        public float getCoeficienteIluminacao() {
            return coeficienteIluminacao;
        }

        public float getIluminacaoPropria() {
            return iluminacaoPropria;
        }

        public boolean isEletrocutavel() {
            return eletrocutavel;
        }

        public boolean temBorda() {
            return imagens.length > 1;
        }

        public int getPrioridade() {
            return prioridade;
        }

        @Override
        public String toString() {
            return this.nome;
        }

        public static class ReferenciaImagem implements Serializable {

            private static final long serialVersionUID = 5189423065789330673L;
            @RealName("Código")
            public int codigo;
            
            @RealName("Ângulo")
            public int angulo;

            private ReferenciaImagem() {
            }

            public ReferenciaImagem(int codigo, int angulo) {
                this.codigo = codigo;
                this.angulo = angulo;
            }

            @Override
            public String toString() {
                return this.codigo + ":" + this.angulo;
            }
        }
    }

    public static class RacaInfo implements Serializable {

        private static final long serialVersionUID = 2599918195446419095L;
        private String nome;
        private Texture[] textures;
        private float indiceIdiomatico;
        //Adicionar mais coisas... Caracter�sticas b�sicas

        public String getNome() {
            return nome;
        }

        public Texture[] getTextures() {
            return textures;
        }

        public float getIndiceIdiomatico() {
            return indiceIdiomatico;
        }
    }

    public static class ElementoInfo implements Serializable {

        private static final long serialVersionUID = -2736576267497470555L;
        @RealName("Name")
        private String nome;
        
        @RealName("Description")
        private String descricao;
        
        @RealName("Image")
        private int codTextura; //textura no ItemLoader
        
        @RealName("Main Color")
        private Color mainColor; //for the generation of equips
        
        @RealName("Type")
        private Tipo tipo;
        
        @RealName(value="Composition", nullable=true)
        private Composicao[] composicao; //se for null, é um elemento básico, não pode ser criado
        
        //atributos do elemento
        /*
         * Hardness (scratch)            Hs \
         * Hardness (concussion)         Hc-|- For damage calculation
         * Brittleness                   Br /
         * Electric Conductivity         EC - For damage in electrical attacks/enviroment
         * Medium Frequency of Occurence FO - For generation in planets, price calculation, rarity calculation
         * Magnectic Coefficient*        MC - For attraction in magnetic enviroment (or of ferrous objects), crafting
         * Melting Point                 MP \_For damage in hot enviroments, crafting
         * Boiling Point                 BP /
         * Density                       De - For Radiation Resistence and mass calculation
         * 
         * * ]0, 1[, where:
         *    0 -> non ferromagnect;
         *   -1 -> the strongest magnet possible (only theoretical);
         *    1 -> the strongest ferromagnect metal possible (only theoretical).
         *   Strongest elements: iron, nickel, cobalt
         */
        @RealName("Attributes")
        @ArrayType.FixedNames({"Hs", "Hc", "Br", "EC", "FO", "MC", "MP", "BP", "De"})
        private float[] atributos;
        
        public enum Tipo {
            @RealName("Metal") METAL,
            @RealName("Polymer") POLIMERO,
            @RealName("Ingredient") INGREDIENTE;
        }
        
        //formação do elemento
        public static class Composicao {
            @RealName("Porcentagem") @NumberRestrictions(min=0, minCap=Cap.INCLUSIVE, max=1, maxCap=Cap.INCLUSIVE, integer=false)
            private float porcentagem;
            
            @RealName("Elemento") @CodeDefinition(className="game.Informacoes$ElementoInfo")
            private int codElemento;
            
            private Composicao() {}

            public Composicao(float porcentagem, int codElemento) {
                this.porcentagem = porcentagem;
                this.codElemento = codElemento;
            }

            public int getCodElemento() {
                return codElemento;
            }

            public float getPorcentagem() {
                return porcentagem;
            }
        }

        private ElementoInfo() {
        }

        public ElementoInfo(String nome, String descricao, int codTextura, Color mainColor) {
            this.nome = nome;
            this.descricao = descricao;
            this.codTextura = codTextura;
            this.mainColor = mainColor;
        }

        public ElementoInfo(String nome, String descricao, int codTextura, Color mainColor, Tipo tipo, Composicao[] composicao, float[] atributos) {
            this.nome = nome;
            this.descricao = descricao;
            this.codTextura = codTextura;
            this.mainColor = mainColor;
            this.tipo = tipo;
            this.composicao = composicao;
            this.atributos = atributos;
        }

        public String getNome() {
            return nome;
        }

        public String getDescricao() {
            return descricao;
        }

        public int getCodTextura() {
            return codTextura;
        }

        public Color getMainColor() {
            return mainColor;
        }

        public Composicao[] getComposicao() {
            return composicao;
        }

        public Tipo getTipo() {
            return tipo;
        }
        
        public float getAtributo(int c) {
            return atributos[c];
        }
        
        @Override
        public String toString() {
            return this.nome;
        }
    }

    public static class ItemInfo implements Serializable {

        private static final long serialVersionUID = -468240643338173313L;
        private String nome, descricao, regex;
        private float indiceRaridade, indicePreco, massa, volume;
        private Texture imagem;

        public String getNome() {
            return nome;
        }

        public String getDescricao() {
            return descricao;
        }

        public String getRegex() {
            return regex;
        }

        public float getIndiceRaridade() {
            return indiceRaridade;
        }

        public float getIndicePreco() {
            return indicePreco;
        }

        public float getMassa() {
            return massa;
        }

        public float getVolume() {
            return volume;
        }

        public Texture getImagem() {
            return imagem;
        }
    }

    public static class EspecieInfo implements Serializable {

        private static final long serialVersionUID = 7211553689757643491L;
        private String nome;
        private Tamanho tamanho;
        private Forma forma;
        private int formaM, formaN;
        private GasAtmosferico[] gasesNecessarios;
        private float faixaLuzIni, faixaLuzFim;
        private Energia[] obtencoesEnergia;
        private int[] input, output; //ver folha ACD14
        private Reproducao reproducao;
        private Texture[] imagens;

        public EspecieInfo(String nome, Tamanho tamanho, Forma forma, int formaM, int formaN, GasAtmosferico[] gasesNecessarios, float faixaLuzIni, float faixaLuzFim, Energia[] obtencoesEnergia, int[] input, int[] output, Reproducao reproducao, Texture[] imagens) {
            this.nome = nome;
            this.tamanho = tamanho;
            this.forma = forma;
            this.formaM = formaM;
            this.formaN = formaN;
            this.gasesNecessarios = gasesNecessarios;
            this.faixaLuzIni = faixaLuzIni;
            this.faixaLuzFim = faixaLuzFim;
            this.obtencoesEnergia = obtencoesEnergia;
            this.input = input;
            this.output = output;
            this.reproducao = reproducao;
            this.imagens = imagens;
        }

        public String getNome() {
            return nome;
        }

        public Texture[] getImagens() {
            return imagens;
        }

        public Tamanho getTamanho() {
            return tamanho;
        }

        public Forma getForma() {
            return forma;
        }

        public int getFormaM() {
            return formaM;
        }

        public int getFormaN() {
            return formaN;
        }

        public GasAtmosferico[] getGasesNecessarios() {
            return gasesNecessarios;
        }

        public float getFaixaLuzIni() {
            return faixaLuzIni;
        }

        public float getFaixaLuzFim() {
            return faixaLuzFim;
        }

        public Energia[] getObtencoesEnergia() {
            return obtencoesEnergia;
        }

        public int[] getInput() {
            return input;
        }

        public int[] getOutput() {
            return output;
        }

        public Reproducao getReproducao() {
            return reproducao;
        }

        //Enums
        public enum Tamanho {

            MINUSCULO, MUITO_PEQUENO, PEQUENO, MEDIO, GRANDE, GIGANTE;
        }

        public enum Forma {

            AMEBA, N_UPLEDE, HUMANOIDE, BIPEDE;
        }

        public enum Energia {

            FOTOSSINTESE, QUIMIOSSINTESE, RESPIRACAO;
        }

        public enum Reproducao {

            ASSEXUADA, SEXUADA, AMBAS;
        }
    }

    public static class MissaoImportante implements Serializable {

        private static final long serialVersionUID = 4130513867841728105L;
        private Missao missao;
        private EntidadeNave ondePega;
        private MissaoImportante[] resultados;

        public Missao getMissao() {
            return missao;
        }

        public EntidadeNave getOndePega() {
            return ondePega;
        }

        public MissaoImportante[] getResultados() {
            return resultados;
        }
    }
}
